剑指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;
    }

 

  

 

  

 

  

 

posted @ 2019-09-07 15:43  weisman  阅读(197)  评论(0编辑  收藏  举报