剑指offer-下
丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
public int GetUglyNumber_Solution(int index) { if(index==0)return 0; ArrayList<Integer> list=new ArrayList<Integer>(); list.add(1); int i2=0,i3=0,i5=0;//存放三个队列的头位置 while(list.size()<index)//循环的条件 { int m2=list.get(i2)*2; int m3=list.get(i3)*3; int m5=list.get(i5)*5; int min=Math.min(m2,Math.min(m3,m5)); list.add(min); if(min==m2)i2++; if(min==m3)i3++; if(min==m5)i5++; } return list.get(list.size()-1); }
第一个只出现一次的字符
public int FirstNotRepeatingChar(String str) { int count=-1; int index=0; int[] chars = new int[128]; for(int i=0;i<str.length();i++){ index=(int)str.charAt(i); if(chars[index]==0){ chars[index]=count; count--; } else {chars[index]=1;count--;} } int max=-10001; int min_index=0; for(int i=0;i<128;i++){ if(chars[i]<0 && chars[i]>max){ max=chars[i]; min_index = i; } } if(max==-10001) return -1; return max*-1-1; }
// 思路是初始的int[] 记录出现的位置(负值),再次出现后转为正值。之后就是找负值最大的就是最先出现的
数组中的逆序对
input: 1,2,3,4,5,6,7,0
output: 7
归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,
合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
参考剑指Offer,但是感觉剑指Offer归并过程少了一步拷贝过程。
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余
public class Solution { public int InversePairs(int [] array) { if(array==null || array.length<=1) return 0; int[] copy = new int[array.length]; System.arraycopy(array, 0, copy, 0, array.length); return mergeSort(array, copy, 0, array.length-1); } private int mergeSort(int [] array, int [] copy, int start, int end){ if(start>=end) return 0; int mid = (end+start)>>1;//等价于/2 int left = mergeSort(array, copy, start, mid); int right = mergeSort(array, copy, mid+1, end); int foreidx = mid; //前半部分指标 int backidx = end; //后半部分指标 int counts = 0; //记录本次逆序对数量 int idxcopy = end; //辅助数组的下标 while(foreidx>=start && backidx >= mid+1){ if(array[foreidx] > array[backidx]){ copy[idxcopy--] = array[foreidx--]; counts += backidx - mid; if(counts>1000000007) counts %= 1000000007; } else copy[idxcopy--] = array[backidx--]; } while(foreidx>=start){ copy[idxcopy--] = array[foreidx--]; } while(backidx>mid){ copy[idxcopy--] = array[backidx--]; } for(int i=start;i<=end;i++){ array[i] = copy[i]; } return (left+right+counts)% 1000000007; } }
两个链表第一个公共结点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { ListNode p1=pHead1,p2=pHead2; while(p1!=p2){ p1 = p1==null?pHead2:p1.next; p2 = p2==null?pHead1:p2.next; } return p1; }
统计数字出现次数
public int GetNumberOfK(int [] array , int k) { int count=0; char kk = (char)(k+'0'); StringBuilder sb = new StringBuilder(); for(int i=0;i<array.length;i++){ sb.append(array[i]+""); } for(int i=0;i<sb.length();i++){ if(sb.charAt(i)==kk) count++; } return count; }
二叉树的深度
非递归解法,层次遍历求深度
/** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ 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; } }
递归解法
import java.lang.Math; public class Solution { public int TreeDepth(TreeNode pRoot) { if(pRoot == null) return 0; int left = TreeDepth(pRoot.left); int right = TreeDepth(pRoot.right); return Math.max(left, right) + 1; } }
数组中只出现一次的两个数
位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。
将所有所有数字相异或,则最后的结果肯定是那两个只出现一次的数字异或的结果
依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,
剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。
我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。
如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。
然后把这两个组分别逐位异或,剩余的两个结果就是这两个只出现一次的数字。
//num1,num2分别为长度为1的数组。传出参数 //将num1[0],num2[0]设置为返回结果 public class Solution { //位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。 //将所有所有数字相异或,则最后的结果肯定是那两个只出现一次的数字异或的结果 /*依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或, 剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。 我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。 如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。 然后把这两个组分别逐位异或,剩余的两个结果就是这两个只出现一次的数字。 */ public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { if(array.length<2) return ; int n = array.length; int temp = array[0]; for(int i=1;i<n;i++){ temp = temp^array[i]; } if(temp==0) return ; int index=0; while((temp&1)==0){ temp=temp>>1; ++index; } num1[0] = 0; num2[0] = 0; for(int i=0;i<n;i++){ if(IsBit(array[i],index)) num1[0]^=array[i]; else num2[0]^=array[i]; } } private boolean IsBit(int num, int index){ num = num>>index; return ( (num&1)==1 ); } }
和为S的正数序列
双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,我们根据窗口内值之和来确定窗口的位置和宽度。非常牛逼的思路,虽然双指针或者所谓的滑动窗口技巧还是蛮常见的
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) { //存放结果 ArrayList<ArrayList<Integer> > result = new ArrayList<>(); //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小 int plow = 1,phigh = 2; while(phigh > plow){ //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2 int cur = (phigh + plow) * (phigh - plow + 1) / 2; //相等,那么就将窗口范围的所有数添加进结果集 if(cur == sum){ ArrayList<Integer> list = new ArrayList<>(); for(int i=plow;i<=phigh;i++) list.add(i); result.add(list); plow++; //如果当前窗口内的值之和小于sum,那么右边窗口右移一下 }else if(cur < sum) phigh++; else{ //如果当前窗口内的值之和大于sum,那么左边窗口右移一下 plow++; } } return result; }
和为S的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
和相同的情况下,外围的两个数的乘积最小,采用双指针夹逼法
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
//和相同的情况下,外围的两个数的乘积最小,采用双指针夹逼法
ArrayList<Integer> list = new ArrayList<>();
if (array == null || array.length == 0)
return list;
int left = 0;
int right = array.length - 1; //和上一道题有所不同,结尾的位置在最后。但是在2也是可以的
while (left < right) {
int total = array[left] + array[right];
if (total == sum) {
list.add(array[left]);
list.add(array[right]);
return list;
} else if (total > sum) {
//大于sum,说明太大了,right左移寻找更小的数
--right;
} else {
//2.如果和小于sum,说明太小了,left右移寻找更大的数
++left;
}
}
return list;
}
左旋转字符串
对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。
方法:两次旋转法
//两次旋转法 public String LeftRotateString(String str, int n) { char[] chars = str.toCharArray(); if(chars.length < n) return ""; reverse(chars, 0, n - 1); reverse(chars, n, chars.length - 1); reverse(chars, 0, chars.length - 1); return new String(chars); } public void reverse(char[] chars, int start, int end) { while (start < end) { char temp = chars[start]; chars[start] = chars[end]; chars[end] = temp; start++; end--; } }
反转单词序列
“student. a am I”。----------》I am a student.”
public String ReverseSentence(String str) { if(str.trim().equals("")){ return str; } String[] a = str.split(" "); StringBuffer o = new StringBuffer(); int i; for (i = a.length; i >0;i--){ o.append(a[i-1]); if(i > 1){ o.append(" "); } } return o.toString(); }
环的入口结点
public ListNode EntryNodeOfLoop(ListNode pHead) { if(pHead==null|| pHead.next==null|| pHead.next.next==null)return null; ListNode fast=pHead.next.next; ListNode slow=pHead.next; /////先判断有没有环 while(fast!=slow){ if(fast.next!=null&& fast.next.next!=null){ fast=fast.next.next; slow=slow.next; } else{ //没有环,返回 return null; } } //循环出来的话就是有环,且此时fast==slow. fast=pHead; while(fast!=slow){ fast=fast.next; slow=slow.next; } return slow; }
删除重复节点
public ListNode deleteDuplication(ListNode pHead) { if(pHead==null || pHead.next==null) return pHead; ListNode current = null; if(pHead.val==pHead.next.val){ current = pHead.next.next; while(current!=null && current.val==pHead.val){// 注意判断条件的先后次序 current = current.next; } return deleteDuplication(current); } else { current = pHead.next; pHead.next = deleteDuplication(current); return pHead; }
中序遍历下一个结点
public TreeLinkNode GetNext(TreeLinkNode pNode) { if(pNode==null) return null; if(pNode.right!=null){ pNode = pNode.right; while(pNode.left!=null) pNode = pNode.left; return pNode; } while(pNode.next!=null){ TreeLinkNode proot = null; while(pNode.next!=null){ proot = pNode.next; if(pNode==proot.left) return proot; pNode = pNode.next; } } return null; }
判断镜像二叉树
boolean isSymmetrical(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; }
之字形打印二叉树
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> res = new ArrayList<>(); LinkedList<TreeNode> list = new LinkedList<>();// 存储节点,每次一层 if(pRoot != null) list.add(pRoot); boolean flag = true;// true 从左到右 while (!list.isEmpty()) { int size = list.size(); ArrayList<Integer> layer = new ArrayList<>(); if (flag) for (int i = 0; i < size; i++) layer.add(list.get(i).val); else for (int i = size - 1; i >= 0; i--) layer.add(list.get(i).val); flag = !flag; res.add(layer); for (int i = 0; i < size; i++) { TreeNode node = list.poll(); if (node.left != null) list.add(node.left); if (node.right != null) list.add(node.right); } } return res; }
逐层打印二叉树
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> res = new ArrayList<>(); LinkedList<TreeNode> list = new LinkedList<>();// 存储节点,每次一层 if(pRoot != null) list.add(pRoot); while (!list.isEmpty()) { int size = list.size(); ArrayList<Integer> layer = new ArrayList<>(); for (int i = 0; i < size; i++) layer.add(list.get(i).val); res.add(layer); for (int i = 0; i < size; i++) { TreeNode node = list.poll(); if (node.left != null) list.add(node.left); if (node.right != null) list.add(node.right); } } return res; }
二叉搜索树第k个结点
//思路:二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。 // 所以,按照中序遍历顺序找到第k个结点就是结果。 public class Solution { int index = 0; //计数器 TreeNode KthNode(TreeNode root, int k) { if(root != null){ //中序遍历寻找第k个 TreeNode node = KthNode(root.left,k); if(node != null) return node; index ++; if(index == k) return root; node = KthNode(root.right,k); if(node != null) return node; } return null; } }
数组中重复的数字
public boolean duplicate(int numbers[],int length,int [] duplication) { boolean [] k = new boolean[length]; for(int i=0;i<length;i++){ if(k[numbers[i]]==true) { duplication[0]=numbers[i]; return true;} else k[numbers[i]]=true; } return false; }
剪绳子
/** * 题目分析: * 先举几个例子,可以看出规律来。 4 : 2*2 * 5 : 2*3 * 6 : 3*3 * 7 : 2*2*3 或者4*3 * 8 : 2*3*3 * 9 : 3*3*3 * 10:2*2*3*3 或者4*3*3 * 11:2*3*3*3 * 12:3*3*3*3 * 13:2*2*3*3*3 或者4*3*3*3 * * 下面是分析: 首先判断k[0]到k[m]可能有哪些数字,实际上只可能是2或者3。 * 当然也可能有4,但是4=2*2,我们就简单些不考虑了。 * 5<2*3,6<3*3,比6更大的数字我们就更不用考虑了,肯定要继续分。 * 其次看2和3的数量,2的数量肯定小于3个,为什么呢?因为2*2*2<3*3,那么题目就简单了。 * 直接用n除以3,根据得到的余数判断是一个2还是两个2还是没有2就行了。 * 由于题目规定m>1,所以2只能是1*1,3只能是2*1,这两个特殊情况直接返回就行了。 * * 乘方运算的复杂度为:O(log n),用动态规划来做会耗时比较多。 */ public int cutRope(int target) { if(target<2) return target; if(target==2) return 1; if(target==3) return 2; if(target==4) return 4; int three = (int)(target/3); int two = (int)((target-three*3)/2); int result = (int)(Math.pow(3,three)) * (int)(Math.pow(2,two)); return result; }
机器人的运动范围
public int movingCount(int threshold, int rows, int cols) { boolean [][] visited = new boolean[rows][cols]; return countingSteps(threshold,rows,cols,0,0,visited); } private int countingSteps(int limit,int rows,int cols,int r,int c,boolean[][] visited){ if (r < 0 || r >= rows || c < 0 || c >= cols|| visited[r][c] || bitSum(r) + bitSum(c) > limit) return 0; visited[r][c] = true; return countingSteps(limit,rows,cols,r - 1,c,visited) + countingSteps(limit,rows,cols,r,c - 1,visited) + countingSteps(limit,rows,cols,r + 1,c,visited) + countingSteps(limit,rows,cols,r,c + 1,visited) + 1; } private int bitSum(int t){ int count = 0; while (t != 0){ count += t % 10; t /= 10; } return count; }