剑指offer编程题66道题 36-66
36.两个链表的第一个公共节点
题目描述

/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ import java.util.*; public class Solution { public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { Stack<ListNode> s1=new Stack<ListNode>(); Stack<ListNode> s2=new Stack<ListNode>(); while(pHead1!=null){ s1.add(pHead1); pHead1=pHead1.next; } while(pHead2!=null){ s2.add(pHead2); pHead2=pHead2.next; } ListNode foundNode =null; ListNode node =null; while(!s1.isEmpty() && !s2.isEmpty()){ node=s1.pop(); if(node==s2.pop()) foundNode =node; else return foundNode; } return foundNode; } }
2.利用HashSet中元素不能重复的原理,首先遍历一个链表进行存放,然后遍历另外一个链表,找到第一个与Set中元素相同的节点。

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { ListNode current1 = pHead1; ListNode current2 = pHead2; HashSet <ListNode> hashSet = new HashSet<ListNode>(); while (current1 != null) { hashSet.add(current1); current1 = current1.next; } while (current2 != null) { if (hashSet.contains(current2)) return current2; current2 = current2.next; } return null; }
3.首先遍历两个链表得到它们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个节点。第二次遍历的时候,在较长的链表上线走若干步,接着同时在两个链表上遍历,找到的第一个相同的节点就是它们的第一个公共节点。

链接:https://www.nowcoder.com/questionTerminal/6ab1d9a29e88450685099d45c9e31e46 来源:牛客网 public ListNode FindFirstCommonNodeII(ListNode pHead1, ListNode pHead2) { ListNode current1 = pHead1;// 链表1 ListNode current2 = pHead2;// 链表2 if (pHead1 == null || pHead2 == null) return null; int length1 = getLength(current1); int length2 = getLength(current2); // 两连表的长度差 // 如果链表1的长度大于链表2的长度 if (length1 >= length2) { int len = length1 - length2; // 先遍历链表1,遍历的长度就是两链表的长度差 while (len > 0) { current1 = current1.next; len--; } } // 如果链表2的长度大于链表1的长度 else if (length1 < length2) { int len = length2 - length1; // 先遍历链表1,遍历的长度就是两链表的长度差 while (len > 0) { current2 = current2.next; len--; } } //开始齐头并进,直到找到第一个公共结点 while(current1!=current2){ current1=current1.next; current2=current2.next; } return current1; } // 求指定链表的长度 public static int getLength(ListNode pHead) { int length = 0; ListNode current = pHead; while (current != null) { length++; current = current.next; } return length; }
37.数字在排序数组中出现的次数
题目描述

public class Solution { public int GetNumberOfK(int [] array , int k) { int number = 0; int flag = 0; for(int i: array){ if(i == k){ number++; flag = 1; }else if(i != k && flag == 1){ return number; } } return number; } }
二分法:

public class Solution { public int GetNumberOfK(int [] array , int k) { int length = array.length; if(length == 0){ return 0; } int firstK = getFirstK(array, k, 0, length-1); int lastK = getLastK(array, k, 0, length-1); if(firstK != -1 && lastK != -1){ return lastK - firstK + 1; } return 0; } //递归写法 private int getFirstK(int [] array , int k, int start, int end){ if(start > end){ return -1; } int mid = (start + end) >> 1; if(array[mid] > k){ return getFirstK(array, k, start, mid-1); }else if (array[mid] < k){ return getFirstK(array, k, mid+1, end); }else if(mid-1 >=0 && array[mid-1] == k){ return getFirstK(array, k, start, mid-1); }else{ return mid; } } //循环写法 private int getLastK(int [] array , int k, int start, int end){ int length = array.length; int mid = (start + end) >> 1; while(start <= end){ if(array[mid] > k){ end = mid-1; }else if(array[mid] < k){ start = mid+1; }else if(mid+1 < length && array[mid+1] == k){ start = mid+1; }else{ return mid; } mid = (start + end) >> 1; } return -1; } }

public class Solution { public int GetNumberOfK(int [] array , int k) { int first = firstGreatOrEqual(array, k); int last = firstGreat(array, k); return last - first; } public int firstGreatOrEqual(int[] array,int key) { int left = 0; int right = array.length - 1; while (left <= right) { int mid = (left + right) / 2; if (array[mid] >= key) { right = mid - 1; } else { left = mid + 1; } } return left; } public int firstGreat(int[] array, int key) { int left = 0; int right = array.length - 1; while (left <= right) { int mid = (left + right) / 2; if (array[mid] > key) { right = mid - 1; } else { left = mid + 1; } } return left; } }
38.二叉树的深度
题目描述

/** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { public int TreeDepth(TreeNode root) { if(root == null) return 0; int left = TreeDepth(root.left); int right = TreeDepth(root.right); return (left>right)?(left+1):(right+1); } }
2.采用层次遍历的方式
先知道下一层树的个数,然后count++,相等时候,结束一层的层序遍历。

链接:https://www.nowcoder.com/questionTerminal/435fb86331474282a3499955f0a41e8b 来源:牛客网 import java.util.Queue; import java.util.LinkedList; public class Solution { public int TreeDepth(TreeNode pRoot) { if(pRoot == null){ return 0; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(pRoot); int depth = 0, count = 0, nextCount = 1; while(queue.size()!=0){ TreeNode top = queue.poll(); count++; if(top.left != null){ queue.add(top.left); } if(top.right != null){ queue.add(top.right); } if(count == nextCount){ nextCount = queue.size(); count = 0; depth++; } } return depth; } }
39.平衡二叉树
题目描述

public class Solution { private boolean isBalanced = true; public boolean IsBalanced_Solution(TreeNode root) { height(root); return isBalanced; } private int height(TreeNode root){ if(root == null) return 0; int left = height(root.left); int right = height(root.right); if(Math.abs(left-right)>1) isBalanced = false; return 1+Math.max(left,right); } }
40.数组中只出现一次的数字
题目描述
思路:
//使用堆栈来做辅助功能,将数组先排序,依次入栈,每一次数组入栈时和当前堆栈的栈头比较,如果当前堆栈为空,就入栈,如果和当前栈头的元素相同就出栈,当数组中左右元素都入栈完毕,那么当前栈中剩余的2个元素就是只出现一次的两个元素
时间复杂度为O(n2),空间复杂度为O(n)

//num1,num2分别为长度为1的数组。传出参数 //将num1[0],num2[0]设置为返回结果 import java.util.*; public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { Arrays.sort(array); Stack<Integer> stack = new Stack<Integer>(); int len = array.length; if(array == null){ num1[0] = 0; num2[0] = 0; } for(int x = 0;x<len;x++){ if(stack.isEmpty()){ stack.push(array[x]); }else{ if(stack.peek() == array[x]) stack.pop(); else stack.push(array[x]); } } num1[0] = stack.pop(); num2[0] = stack.pop(); } }
用ArrayList,思路和前面的一致
时间复杂度为O(n2),空间复杂度为O(n)
remove不装箱的话会被当作按照下标删除,add会自动装箱

链接:https://www.nowcoder.com/questionTerminal/e02fdb54d7524710a7d664d082bb7811 来源:牛客网 import java.util.ArrayList; public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { ArrayList<Integer>list=new ArrayList<Integer>(); for(int i=0;i<array.length;i++) { if(!list.contains(array[i])) list.add(array[i]); else list.remove(new Integer(array[i])); } if(list.size()>1) { num1[0]=list.get(0); num2[0]=list.get(1); } } }
最好的方法:
时间复杂度为O(n),空间复杂度为O(1)

//num1,num2分别为长度为1的数组。传出参数 //将num1[0],num2[0]设置为返回结果 import java.util.*; public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { int diff = 0; for(int num : array) diff ^= num; for(int num : array){ if((num & diff) == 0) num1[0] ^= num; else num2[0] ^= num; } } }
41.和为S的连续证书序列
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

链接:https://www.nowcoder.com/questionTerminal/c451a3fd84b64cb19485dad758a55ebe 来源:牛客网 import java.util.ArrayList; /* *初始化small=1,big=2; *small到big序列和小于sum,big++;大于sum,small++; *当small增加到(1+sum)/2是停止 */ public class Solution { public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) { ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>(); if(sum<=1){return lists;} int small=1; int big=2; while(small!=(1+sum)/2){ //当small==(1+sum)/2的时候停止 int curSum=sumOfList(small,big); if(curSum==sum){ ArrayList<Integer> list=new ArrayList<Integer>(); for(int i=small;i<=big;i++){ list.add(i); } lists.add(list); small++;big++; }else if(curSum<sum){ big++; }else{ small++; } } return lists; } public int sumOfList(int head,int leap){ //计算当前序列的和 int sum=head; for(int i=head+1;i<=leap;i++){ sum+=i; } return sum; } }
42.和为S 的两个数字
题目描述

import java.util.ArrayList; public class Solution { public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) { ArrayList<Integer> al = new ArrayList<Integer>(); if(array == null) return al; int ahead = 0; int behind = array.length-1; while(ahead < behind){ int curSum = array[ahead] + array[behind]; if(sum == curSum){ al.add(array[ahead]); al.add(array[behind]); break; }else if(curSum > sum){ behind--; }else{ ahead++; } } return al; } }
43.左旋转字符串
题目描述

public class Solution { public String LeftRotateString(String str,int n) { if(str.length() == 0) return ""; char[] a = str.toCharArray(); char[] result = new char[a.length]; for(int i=0;i<a.length-n;i++){ result[i] = a[n+i]; } for(int i=0;i<n;i++){ result[a.length-n+i] = a[i]; } return String.valueOf(result); } }

public class Solution { public String LeftRotateString(String str,int n) { if(str.length()==0) return ""; int len = str.length(); str += str; return str.substring(n, len+n); } }

public class Solution { public String LeftRotateString(String str,int n) { if(str.length()==0) return ""; //把原字符串截取成俩字符串,然后拼接 String s1 = str.substring(0, n); String s2 = str.substring(n,str.length()); return s2 + s1; } }
44.翻转单词顺序列
题目描述

public class Solution { public String ReverseSentence(String str) { if(str==null||str.length()==0||str.trim().length()==0) return str; String[] a = str.split(" "); StringBuffer sb = new StringBuffer(); for(int i = 0;i<a.length;i++){ if(i!=a.length-1) sb.append(String.valueOf(a[a.length-1-i])).append(" "); else sb.append(String.valueOf(a[a.length-1-i])); } return sb.toString(); } }
45.扑克牌算子
思路1:
1.将数组排序
2.找到第一个不出现0的位置,记作min
3.max - min < 5 则是顺子

import java.util.Arrays; public class Solution { public boolean isContinuous(int [] numbers) { if(numbers == null || numbers.length == 0) return false; Arrays.sort(numbers); int length = numbers.length; int positionFlag = 0; boolean endZeroFlag = true; for(int i= 0; i<length-1;i++){ if(numbers[i] == 0){ positionFlag = i+1; endZeroFlag = false; }else { endZeroFlag = true; } if(endZeroFlag == true && numbers[i] == numbers[i+1]) return false; } if(numbers[length-1] - numbers[positionFlag] >= 5) return false; else return true; } }
思路2:

链接:https://www.nowcoder.com/questionTerminal/762836f4d43d43ca9deb273b3de8e1f4 来源:牛客网 import java.util.Arrays; public class Solution { public boolean isContinuous(int[] numbers) { int numOfZero = 0; int numOfInterval = 0; int length = numbers.length; if(length == 0){ return false; } Arrays.sort(numbers); for (int i = 0; i < length - 1; i++) { // 计算癞子数量 if (numbers[i] == 0) { numOfZero++; continue; } // 对子,直接返回 if (numbers[i] == numbers[i + 1]) { return false; } numOfInterval += numbers[i + 1] - numbers[i] - 1; } if (numOfZero >= numOfInterval) { return true; } return false; } }
46.圆圈中最后剩下的数字
0,1,2,。。。n这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字,求出这个圆圈里剩下的最后一个数字。
1.数组来模拟环

public static int findLastNumber(int n,int m){ if(n<1||m<1) return -1; int[] array = new int[n]; int i = -1,step = 0, count = n; while(count>0){ //跳出循环时将最后一个元素也设置为了-1 i++; //指向上一个被删除对象的下一个元素。 if(i>=n) i=0; //模拟环。 if(array[i] == -1) continue; //跳过被删除的对象。 step++; //记录已走过的。 if(step==m) { //找到待删除的对象。 array[i]=-1; step = 0; count--; } } return i;//返回跳出循环时的i,即最后一个被设置为-1的元素 }
2.用ArrayList做
index = (index + m) % data.size();

import java.util.ArrayList; public class Solution { public int LastRemaining_Solution(int n, int m) { if (m == 0 || n == 0) { return -1; } ArrayList<Integer> data = new ArrayList<Integer>(); for (int i = 0; i < n; i++) { data.add(i); } int index = -1; while (data.size() > 1) { index = (index + m) % data.size(); data.remove(index); index--; } return data.get(0); } }
47.求1+2+3+...+n
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路1:
通常求求1+2+3+...+n除了用(乘法)公式n(n+1)/2,无外乎循环和递归两种思路,由于已经明确限制for和while的使用,循环已经不能再用了。递归函数也需要用if或者条件判断语句判断是继续递归下去还是终止递归,但是现在题目已经不允许使用这两种语句。
1
.需利用逻辑与的短路特性实现递归终止。
2
.当n==
0
时,(n>
0
)&&((sum+=Sum_Solution(n-
1
))>
0
)只执行前面的判断,为
false
,然后直接返回
0
;
3
.当n>
0
时,执行sum+=Sum_Solution(n-
1
),实现递归计算Sum_Solution(n)。

public class Solution { public int Sum_Solution(int n) { int sum = n; boolean ans = (n>0) && ((sum += Sum_Solution(n-1))>0); return sum; } }
思路2:
用异常退出递归

public class Solution { public int Sum_Solution(int n) { return sum(n); } int sum(int n){ try{ int i = 1%n; return n+sum(n-1); } catch(Exception e){ return 0; } } }
思路3:
Math的幂函数,但是底层还是用了乘法

public class Solution { public int Sum_Solution(int n) { n = (int) (Math.pow(n, 2)+n)>>1; return n; } }
48.不做加减乘除做加法
首先看十进制是如何做的: 5+7=12,三步走
第一步:相加各位的值,不算进位,得到2。
第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。
第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。
同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111
第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。
第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。
第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。 继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。

public class Solution { public int Add(int num1,int num2) { while (num2!=0) { int temp = num1^num2; num2 = (num1&num2)<<1; num1 = temp; } return num1; } }
49.将字符串转换成整数
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
思路:
1.首先判断字符串第一个字符是不是‘-’‘+’号,如果是,则从第二个字符开始判断是不是数字;如果是‘-’,记录负数的标记
2.遍历每一个数字的asc码,if
(a[i] <
48
|| a[i] >
57
)
return
0
;

public class Solution { public int StrToInt(String str) { if (str.equals("") || str.length() == 0) return 0; char[] a = str.toCharArray(); int sum = 0; int fuhao = 0; boolean fushu = false; if(a[0] == '-'){ fuhao = 1; fushu = true; }else if (a[0] == '+'){ fuhao = 1; } for (int i = fuhao; i < a.length; i++) { if (a[i] < 48 || a[i] > 57) return 0; sum = sum * 10 + a[i] - 48; } return fushu == false ? sum : sum * (-1); } }
犯规写法:

public int StrToInt(String str) { int result; if (str == "" || str == null ) { return 0; } try { result = Integer.valueOf(str); } catch (NumberFormatException e) { return 0; } return result; }
50.数组中重复的数字
题目描述

public class Solution { public boolean duplicate(int numbers[],int length,int [] duplication) { boolean [] a = new boolean[length]; for(int i=0;i<length; i++){ if(a[numbers[i]] == true){ duplication[0] = numbers[i]; return true; } a[numbers[i]] = true; } return false; } }
使用Array.sort()或者HashSet(hashSet.add方法返回的是boolean值),但是补不能保证题目条件不能保证题目条件:如果输入长度为7的数组{ 7 5 6 7 5 3 1},那么对应的输出是第一个重复的数字2。

链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8 来源:牛客网 import java.util.*; public class Solution { public boolean duplicate(int numbers[],int length,int [] duplication) { //方法1: if(numbers == null || numbers.length == 0) return false; Arrays.sort(numbers); int flag = 0;//做标记 for(int i=0;i<length-1;i++) { if(numbers[i] == numbers[i+1]) { duplication[0] = numbers[i]; flag = 1; break; } } return flag == 1? true:false; //方法2: HashSet<Integer> hs = new HashSet<>(); for(int i=0;i<length;i++) { if(!hs.add(numbers[i])) { duplication[0]=numbers[i]; return true; } } return false; } }

import java.util.ArrayList; public class Solution { public int[] multiply(int[] A) { int length = A.length; int [] B = new int[length]; B[0] = 1; //计算下三角连乘 for(int i = 1;i<length;i++){ B[i] = B[i-1]*A[i-1]; } int temp = 1; //计算上三角 for(int j=length -2;j>=0;j--){ temp *= A[j+1]; B[j] *= temp; } return B; } }
52.正则表达式的匹配
思路:
当模式中的第二个字符不是“*”时: 1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。 2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。 而当模式中的第二个字符是“*”时: 如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式: 1、模式后移2字符,相当于x*被忽略; 2、字符串后移1字符,模式后移2字符; 3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位; 这里需要注意的是:Java里,要时刻检验数组是否越界。

public class Solution { public boolean match(char[] str, char[] pattern) { if (str == null || pattern == null) { return false; } int strIndex = 0; int patternIndex = 0; return matchCore(str, strIndex, pattern, patternIndex); } public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) { //有效性检验:str到尾,pattern到尾,匹配成功 if (strIndex == str.length && patternIndex == pattern.length) { return true; } //pattern先到尾,匹配失败 if (strIndex != str.length && patternIndex == pattern.length) { return false; } //模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;如不匹配,模式后移2位 if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') { if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) { return matchCore(str, strIndex, pattern, patternIndex + 2)//模式后移2,视为x*匹配0个字符 || matchCore(str, strIndex + 1, pattern, patternIndex + 2)//视为模式匹配1个字符 || matchCore(str, strIndex + 1, pattern, patternIndex);//*匹配1个,再匹配str中的下一个 } else { return matchCore(str, strIndex, pattern, patternIndex + 2); } } //模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) { return matchCore(str, strIndex + 1, pattern, patternIndex + 1); } return false; } }
53.表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
犯规写法1:使用正则表达式

public class Solution { public boolean isNumeric(char[] str) { String string = String.valueOf(str); return string.matches("[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?"); } }
犯规写法2:使用自带API

public class Solution { public boolean isNumeric(char[] str) { try { double re = Double.parseDouble(new String(str)); } catch (NumberFormatException e) { return false; } return true; } }
正规解法:
参考剑指offer
分成A[.[B]][e|EC]或者.B[e|EC]
分表验证ABC三部分,AC都是可带正负符号的整数,B为无符号整数

//参见剑指offer public class Solution { private int index = 0; public boolean isNumeric(char[] str) { if (str.length < 1) return false; boolean flag = scanInteger(str); if (index < str.length && str[index] == '.') { index++; flag = scanUnsignedInteger(str) || flag; } if (index < str.length && (str[index] == 'E' || str[index] == 'e')) { index++; flag = flag && scanInteger(str); } return flag && index == str.length; } private boolean scanInteger(char[] str) { if (index < str.length && (str[index] == '+' || str[index] == '-') ) index++; return scanUnsignedInteger(str); } private boolean scanUnsignedInteger(char[] str) { int start = index; while (index < str.length && str[index] >= '0' && str[index] <= '9') index++; return start < index; //是否存在整数 } }
54.字符流中第一个不重复的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
思路1:
使用LinkedHashMap实现,注意不能使用hashmap,因为题目要求得到的是第一个不重复的字符,所以必须有序。

import java.util.LinkedHashMap; public class Solution { LinkedHashMap<Character,Integer> map =new LinkedHashMap<Character,Integer>(); public void Insert(char ch) { if(map.containsKey(ch)){ map.put(ch, map.get(ch)+1); }else{ map.put(ch, 1); } } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { for(char key:map.keySet()){ if(map.get(key) == 1) return key; } return '#'; } }
思路2:
一个256大小的数组来实现一个简易的哈希表

public class Solution { int[] hashtable=new int[256]; StringBuffer s=new StringBuffer(); //Insert one char from stringstream public void Insert(char ch) { s.append(ch); if(hashtable[ch]==0) hashtable[ch]=1; else hashtable[ch]+=1; } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { char[] str=s.toString().toCharArray(); for(char c:str) { if(hashtable[c]==1) return c; } return '#'; } }
思路3:
同样使用哈希表,不过非常啊巧妙的借助队列

private int[] cnts = new int[256]; private Queue<Character> queue = new LinkedList<>(); public void Insert(char ch) { cnts[ch]++; queue.add(ch); while (!queue.isEmpty() && cnts[queue.peek()] > 1) queue.poll(); } public char FirstAppearingOnce() { return queue.isEmpty() ? '#' : queue.peek(); }
55.链表中环的入口节点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路1:
设起点到相遇点距离为x,起点到入口点距离为y,环长度为r,则快慢针相遇时,满足2x-x=nr,n为快针在环中转的圈数。--> x=nr
快慢针相遇点距环入口点距离x-y
相遇后,快针从起点重新开始以步长为1速度开始走,经过距离y到达环入口点,慢针走y步后距离环入口点距离为x-y+y=x=nr,即走到了环入口点,两个指针相遇

public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead) { ListNode fast = pHead; ListNode slow = pHead; while(fast !=null && fast.next !=null) { fast = fast.next.next; slow = slow.next; if(fast == slow) { ListNode p = pHead; while( p != slow) { p = p.next; slow = slow.next; } return p; } } return null; } }
思路2:
碉堡的解法:利用HashSet,遍历添加链表中的元素,若遇到重复添加则返回哪个元素。

import java.util.HashSet; public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead) { HashSet<ListNode> set = new HashSet<ListNode>(); while(pHead != null){ if(!set.add(pHead)) return pHead; pHead = pHead.next; } return null; } }
使用ArrayList

List<ListNode> list = new ArrayList<ListNode>(); while(!list.contains(pHead)) { list.add(pHead); if(pHead.next!=null) pHead = pHead.next; else break; } if(pHead.next == null)return null; return pHead;
56.删除链表中重复的节点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
1.递归方法(推荐):

public class Solution { public ListNode deleteDuplication(ListNode pHead) { if (pHead == null || pHead.next == null) { // 只有0个或1个结点,则返回 return pHead; } if (pHead.val == pHead.next.val) { // 当前结点是重复结点 ListNode pNode = pHead.next; while (pNode != null && pNode.val == pHead.val) { // 跳过值与当前结点相同的全部结点,找到第一个与当前结点不同的结点 pNode = pNode.next; } return deleteDuplication(pNode); // 从第一个与当前结点不同的结点开始递归 } else { // 当前结点不是重复结点 pHead.next = deleteDuplication(pHead.next); // 保留当前结点,从下一个结点开始递归 return pHead; } } }
2.非递归方法:

public static ListNode deleteDuplication(ListNode pHead) { ListNode first = new ListNode(-1);//设置一个trick first.next = pHead; ListNode p = pHead; ListNode last = first; while (p != null && p.next != null) { if (p.val == p.next.val) { int val = p.val; while (p!= null&&p.val == val) p = p.next; last.next = p; } else { last = p; p = p.next; } } return first.next; }
57.二叉树的下一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点;
② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。

/* public class TreeLinkNode { int val; TreeLinkNode left = null; TreeLinkNode right = null; TreeLinkNode next = null; 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; //退到了根节点仍没找到,则返回null } }
58.对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:
1.递归算法

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) return t2 == null; if(t2 == null) return false; if(t1.val != t2.val) return false; return isSymmetrical(t1.left,t2.right) && isSymmetrical(t1.right,t2.left); } }
2.使用栈
使用stack来保存成对的节点
2).确定入栈顺序,每次入栈都是成对成对的,如left.left, right.right ;left.rigth,right.left

boolean isSymmetricalDFS(TreeNode pRoot) { if(pRoot == null) return true; Stack<TreeNode> s = new Stack<>(); s.push(pRoot.left); s.push(pRoot.right); while(!s.empty()) { TreeNode right = s.pop();//成对取出 TreeNode left = s.pop(); if(left == null && right == null) continue; if(left == null || right == null) return false; if(left.val != right.val) return false; //成对插入 s.push(left.left); s.push(right.right); s.push(left.right); s.push(right.left); } return true; }
59.按照只字形顺序打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
重点学习:使用堆栈实现层级遍历,然后借助一个标记变量实现反向排序是之字形遍历。
每当使下一个层级的节点入堆栈,记录堆栈中节点的个数,然后取出相应个数的节点。
注意:堆栈中是可以存放null值的,当遍历到null时,直接返回,当null也全部从堆栈中弹出后堆栈为空就代表全部遍历完结束。

import java.util.ArrayList; /* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.Queue; public class Solution { public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> al = new ArrayList<ArrayList<Integer>>(); Queue <TreeNode> q = new LinkedList<>(); q.add(pRoot); boolean reverse = false; while(!q.isEmpty()){ ArrayList<Integer> ll = new ArrayList<Integer>(); int cnt = q.size(); while(cnt-- > 0){ TreeNode node = q.poll(); if(node == null) continue; ll.add(node.val); q.add(node.left); q.add(node.right); } if(reverse) Collections.reverse(ll); reverse = !reverse; if(ll.size() != 0) al.add(ll); } return al; } }
60.把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:跟前面那个类似,只是不用翻转顺序

import java.util.ArrayList; /* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.Queue; 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.add(node.val); queue.add(node.left); queue.add(node.right); } if (list.size() != 0) ret.add(list); } return ret; } }
61.序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树
思路:
序列化:用前序遍历递归,递归的终止条件
if(root == null)
return "#";
反序列化:同样以前序遍历的书序递归构造
递归的终止条件
if(strr[index].equals("#"))
return null;
用一个位置变量index记录遍历到达的位置

public class Solution { private int index = -1; String Serialize(TreeNode root) { if(root == null) return "#"; return root.val +" "+ Serialize(root.left)+" " + Serialize(root.right); } TreeNode Deserialize(String str) { index++; String[] strr = str.split(" "); if(strr[index].equals("#")) return null; TreeNode node = new TreeNode(Integer.valueOf(strr[index])); node.left = Deserialize(str); node.right = Deserialize(str); return node; } }
遍历的时候传入未遍历到不同的字符串

private String deserializeStr; public String Serialize(TreeNode root) { if (root == null) return "#"; return root.val + " " + Serialize(root.left) + " " + Serialize(root.right); } public TreeNode Deserialize(String str) { deserializeStr = str; return Deserialize(); } private TreeNode Deserialize() { if (deserializeStr.length() == 0) return null; int index = deserializeStr.indexOf(" "); String node = index == -1 ? deserializeStr : deserializeStr.substring(0, index); deserializeStr = index == -1 ? "" : 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; }
62.二叉搜索树的第k个节点
给定一颗二叉搜索树,请找出其中的第k小的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。
思路:利用二叉搜索数中序遍历有序的特点,递归的中序遍历第k个节点就是。

public class Solution { private TreeNode ret =null; private int cnt = 0; TreeNode KthNode(TreeNode pRoot, int k) { inorder(pRoot,k); return ret; } void inorder(TreeNode pRoot, int k){ if(pRoot == null || cnt > k) return; inorder(pRoot.left,k); cnt++; if(cnt == k) ret = pRoot; inorder(pRoot.right,k); } }
63.数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
思路:
将数据流分成了两个部分,从中间切分(想象一下数据流有序时的样子),大顶堆里面是小的一半,小顶堆大的一半,当求中位数时只用关心中间的一个数或者两个数,这时关心的数就是堆顶的数

import java.util.PriorityQueue; public class Solution { private int count = 0; private PriorityQueue<Integer> max = new PriorityQueue<Integer>((o1,o2)->(o2-o1)); private PriorityQueue<Integer> min = new PriorityQueue<Integer>(); public void Insert(Integer num) { if(count%2 ==0){ //当数据总数为偶数时,新加入的元素,应当进入小根堆 //(注意不是直接进入小根堆,而是经大根堆筛选后取大根堆中最大元素进入小根堆) //1.新加入的元素先入到大根堆,由大根堆筛选出堆中最大的元素 max.add(num); //2.筛选后的【大根堆中的最大元素】进入小根堆 min.add(max.poll()); }else { //当数据总数为奇数时,新加入的元素,应当进入大根堆 //(注意不是直接进入大根堆,而是经小根堆筛选后取小根堆中最大元素进入大根堆) //1.新加入的元素先入到小根堆,由小根堆筛选出堆中最小的元素 min.add(num); //2.筛选后的【小根堆中的最小元素】进入大根堆 max.add(min.poll()); } count++; } public Double GetMedian() { if(count%2 ==0) return (min.peek()+max.peek())/2.0; else return (double)min.peek(); } }
64.滑动窗口的最大值
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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:朴素的解法:

import java.util.ArrayList; public class Solution { public ArrayList<Integer> maxInWindows(int [] num, int size) { ArrayList<Integer> al = new ArrayList<>(); if(num.length == 0 || size==0) return al; for(int i = 0; i<num.length-size+1;i++){ int [] a = new int[size]; for(int j=0;j<size;j++){ a[j] = num[i+j]; } al.add(max(a)); } return al; } public Integer max(int[] num){ int max = num[0]; for(int i = 1;i<num.length;i++){ if(num[i]>max) max = num[i]; } return max; } }
思路2:大顶堆解法

public ArrayList<Integer> maxInWindows(int[] num, int size) { ArrayList<Integer> ret = new ArrayList<>(); if (size > num.length || size < 1) return ret; PriorityQueue<Integer> heap = new PriorityQueue<>((o1, o2) -> o2 - o1); /* 大顶堆 */ for (int i = 0; i < size; i++) heap.add(num[i]); ret.add(heap.peek()); for (int i = 1, j = i + size - 1; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */ heap.remove(num[i - 1]); heap.add(num[j]); ret.add(heap.peek()); } return ret; }
65.矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路:回溯算法,回溯的思想体现在体现在那个很长的if语句后面的,flag标记的恢复

public class Solution { public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { int flag[] = new int[matrix.length]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (helper(matrix, rows, cols, i, j, str, 0, flag)) return true; } } return false; } private boolean helper(char[] matrix, int rows, int cols, int i, int j, char[] str, int k, int[] flag) { int index = i * cols + j; if (i < 0 || i >= rows || j < 0 || j >= cols || matrix[index] != str[k] || flag[index] == 1) return false; if(k == str.length - 1) return true; flag[index] = 1; if (helper(matrix, rows, cols, i - 1, j, str, k + 1, flag) || helper(matrix, rows, cols, i + 1, j, str, k + 1, flag) || helper(matrix, rows, cols, i, j - 1, str, k + 1, flag) || helper(matrix, rows, cols, i, j + 1, str, k + 1, flag)) { return true; } flag[index] = 0; return false; } }
66.机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路:从0,0开始通过递归遍历上下左右加起来的结果就是最大的连通区域

public class Solution { public int movingCount(int threshold, int rows, int cols) { int flag[][] = new int[rows][cols]; //记录是否已经走过 return helper(0, 0, rows, cols, flag, threshold); } private int helper(int i, int j, int rows, int cols, int[][] flag, int threshold) { if (i < 0 || i >= rows || j < 0 || j >= cols || numSum(i) + numSum(j) > threshold || flag[i][j] == 1) return 0; flag[i][j] = 1; return helper(i - 1, j, rows, cols, flag, threshold) + helper(i + 1, j, rows, cols, flag, threshold) + helper(i, j - 1, rows, cols, flag, threshold) + helper(i, j + 1, rows, cols, flag, threshold) + 1; } private int numSum(int i) { int sum = 0; do{ sum += i%10; }while((i = i/10) > 0); return sum; } }